﻿///////////////////////////////////////////////////////////////////////////////////////////////////
//------------------------------版权声明----------------------------
//
// 此文件为 SuperMap iObjects .NET 的示范代码
// 版权所有：北京超图软件股份有限公司
//------------------------------------------------------------------
//
//-----------------------SuperMap iObjects .NET 示范程序说明--------------------------
//
//1、范例简介：如何基于地形和模型进行剖面分析
//2、示例数据：安装目录\SampleData\ProfileAnalyst\ProfileAnalyst.smwu;
//3、关键类型/成员: 
//      Workspace.Open 方法
//      Workspace.Datasources 属性
//      TerrainLayers.Add 方法
//      Layers.Add 方法
//      Scene.PixelToGlobe 方法
//      Scene.Camera 属性
//      ProfileAnalyst.CalculatePlumbProfile 方法
//      DatasetVector.GetRecordset 方法
//      Recordset.BatchEditor.MaxRecordCount 属性
//      Recordset.BatchEditor.Begin 方法
//      Recordset.BatchEditor.Update 方法
//      Datasets.Create 方法
//      GeoLine.AddPart 方法
//      GeoLine3D.AddPart 方法
//      GeoLine.Style 属性
//      GeoLine3D.Style3D 属性
//      GeoText.TextStyle 属性
//      TrackingLayer.Add 方法
//      SceneControl.MouseEvent 事件
//      SceneControl.MouseMove 事件
//4、使用步骤：
//   (1) 运行应用程序
//   (2) 待场景数据加载完，点击“绘制切割线”按钮，在场景中绘制切割线
//   (3) 绘制切割线后，点击“分析”按钮，进行剖面分析。分析完成后弹出绘制有剖面图的窗体
//   (4) 点击"重置相机"按钮，可以将场景定位到模型所在的范围
//---------------------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////
//------------------------------Copyright Statement----------------------------
//
// SuperMap iObjects .NET Sample Code
// Copyright: SuperMap Software Co., Ltd. All Rights Reserved.
//------------------------------------------------------------------
//
//-----------------------Description--------------------------
//
// 1. Introduction: Perform profile analysis based on terrain and model data
// 2. Data: Installation directory\SampleData\ProfileAnalyst\ProfileAnalyst.smwu;
// 3. Key class/method: 
//      Workspace.Open 
//      Workspace.Datasources 
//      TerrainLayers.Add 
//      Layers.Add 
//      Scene.PixelToGlobe 
//      Scene.Camera 
//      ProfileAnalyst.CalculatePlumbProfile 
//      DatasetVector.GetRecordset 
//      Recordset.BatchEditor.MaxRecordCount 
//      Recordset.BatchEditor.Begin 
//      Recordset.BatchEditor.Update 
//      Datasets.Create 
//      GeoLine.AddPart 
//      GeoLine3D.AddPart 
//      GeoLine.Style 
//      GeoLine3D.Style3D 
//      GeoText.TextStyle 
//      TrackingLayer.Add
//      SceneControl.MouseEvent 
//      SceneControl.MouseMove 
// 4. Step:
// (1) Run application
// (2) After the data is loaded, click "Draw line" to draw a line
// (3) After drawing, click "Analyze" to perform profile analysis
// (4) Click "Reset" to relocate the scene
//---------------------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SuperMap.Data;
using SuperMap.UI;

namespace Ycjw.PipeLine.Map.Tools.Conditions.ProfileAnalyst
{
    public partial class ProfileAnalystForm : Sunny.UI.UIForm
    {
        private Workspace m_workspace;
        private MapControl m_mapControl;
        private Datasource m_datasource;

        // 剖面分析生成的三维剖面线数据集
        // The 3d result profile dataset built by profile anlaysis
        private DatasetVector m_resultProfile3D;

        // 切割线的起点和终点
        // Start and end point of the line
        private Point2D m_startPoint;
        private Point2D m_endPoint;

        // 使用数组存储所有三维剖面线对象
        // Use array to store all geoline 3d object
        private List<GeoLine3D> m_profileLine3Ds = new List<GeoLine3D>();
        // 地形剖面线在m_profileLine3Ds数组中的索引
        // Index of terrain line in m_profileLine3Ds array
        private Int32 m_indexOfSurface = -1;

        // 规定绘制剖面线的区域长（X轴）为1000个单位，高（Y轴）为618个单位
        // The base width of line area(X axis) is 1000 unit while height is (Y axis) is 618
        private const Double m_baseWith = 1000.0;
        private const Double m_baseHeight = 618.0;

        // 规定绘制剖面线的原点为（0,0）
        // Origin point of the line is (0,0)
        private Point2D m_origin = new Point2D(0, 0);

        // 地球的长轴和扁率，用于计算测地线
        // Major axis and flatten ratio of earth
        private Double m_majorAxis;
        private Double m_flatten;

        public ProfileAnalystForm()
        {
            InitializeComponent();
            InitializeComponentResources();
        }

        /// <summary>
        /// 构造函数
        /// Construct method
        /// </summary>
        public ProfileAnalystForm(Workspace workspace, DatasetVector resultProfile3D, Point2D startPoint, Point2D endPoint, Datasource datasource)
        {
            InitializeComponent();
            InitializeComponentResources();
            m_resultProfile3D = resultProfile3D;
            m_startPoint = startPoint;
            m_endPoint = endPoint;
            m_workspace = workspace;
            m_datasource = datasource;
            Initialize();
        }

        private void InitializeComponentResources()
        {
            if (SuperMap.Data.Environment.CurrentCulture == "zh-CN")
            {
                this.Text = "剖面图"; 
            }
        }

        /// <summary>
        /// 未关闭窗体时再次分析时，重新加载
        /// Reload when analysis is performed again
        /// </summary>
        public void Refresh(DatasetVector resultProfile3D, Point2D startPoint, Point2D endPoint)
        {
            m_resultProfile3D = resultProfile3D;
            m_startPoint = startPoint;
            m_endPoint = endPoint;
            Initialize();
        }

        /// <summary>
        /// 初始化和计算
        /// Initialization
        /// </summary>
        private void Initialize()
        {
            try
            {
                // 添加地图控件到窗体
                // Add map control
                if (m_mapControl == null)
                {
                    m_mapControl = new MapControl();
                    this.Controls.Add(m_mapControl);
                    m_mapControl.Dock = DockStyle.Fill;
                }
                m_mapControl.Map.Workspace = m_workspace;

                // 获取所有剖面线，写入一个数组，并记录地形剖面线的索引
                // Get line and add them into a array
                m_profileLine3Ds = GetProfileLine3D();

                // 创建用于存储剖面图的CAD数据集，获取记录集
                // Create CAD dataset used to store profile graph
                String resultName = "ProfileResult";
                DatasetVector profileCAD = CreateProfileCAD(resultName);
                Recordset profileRecords = profileCAD.GetRecordset(false, CursorType.Dynamic);

                // 获取长半径和扁率
                // Get Axis and flatten ratio
                m_majorAxis = m_resultProfile3D.PrjCoordSys.GeoCoordSys.GeoDatum.GeoSpheroid.Axis;
                m_flatten = m_resultProfile3D.PrjCoordSys.GeoCoordSys.GeoDatum.GeoSpheroid.Flatten;

                // 计算二维剖面图
                // Calculate 2D profile graph
                List<Geometry> geometris = CalculateProfile();

                // 写入CAD数据集
                // Write CAD dataset
                Draw(geometris, profileRecords);

                // 添加到地图控件中
                // Add them into map control
                m_mapControl.Map.Layers.Clear();
                m_mapControl.Map.Layers.Add(profileCAD, true);
                m_mapControl.Map.IsOverlapDisplayed = true;
                m_mapControl.Map.ViewEntire();
                m_mapControl.Map.Refresh();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        /// <summary>
        /// 从结果剖面线数据集获取所有三维剖面线对象，并记录地形剖面线在数组中的索引
        /// Get all 3D line objects from the result
        /// </summary>
        /// <returns>所有三维剖面线对象数组</returns>
        /// all 3d line object array
        private List<GeoLine3D> GetProfileLine3D()
        {
            try
            {
                Recordset recordset = m_resultProfile3D.GetRecordset(false, CursorType.Static);
                // 使用数组存放三维剖面线对象
                // Use array to store 3D profile line object
                List<GeoLine3D> profileLine3Ds = new List<GeoLine3D>();
                Int32 index = -1;
                while (!recordset.IsEOF)
                {
                    index++;
                    GeoLine3D line = recordset.GetGeometry() as GeoLine3D;
                    profileLine3Ds.Add(line);
                    Boolean isModel = recordset.GetBoolean("IsModel");
                    if (!isModel)
                    {
                        m_indexOfSurface = index;
                    }
                    recordset.MoveNext();
                }
                recordset.Dispose();
                return profileLine3Ds;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
        }

        /// <summary>
        /// 计算二维剖面图，包括坐标轴和刻度、文本以及二维剖面线
        /// Calculate 2D profile graph, including axis, degree, text and line
        /// </summary>
        /// <returns>二维剖面图的对象数组</returns>
        /// Array of 2D profile graph
        private List<Geometry> CalculateProfile()
        {
            try
            {
                // 计算最大距离，实际为切割线的表面长度
                // Calculate maximum distance, the surface length of the line
                Double maxDistance = ComputeMaxDistance(m_startPoint, m_endPoint);

                // 计算剖面线中的最大高程和最小高程
                // Calculate maximum and minimum elevation value
                Double maxElevation = -1000000000;
                Double minElevation = 1000000000;
                ComputeElevation(ref maxElevation, ref minElevation);

                List<Geometry> geometries = ComputeAllElements(maxDistance, maxElevation, minElevation);
                return geometries;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
        }

        /// <summary>
        /// 将剖面图的所有对象添加到结果数据集中
        /// Add all objects of profile graph into result datasets
        /// </summary>
        /// <param name="geometries">剖面图的所有几何对象</param>
        /// All geometry objects in profile graph
        /// <param name="profileRecords">结果数据集的记录集</param>
        /// Records in result datasets
        private void Draw(List<Geometry> geometries, Recordset profileRecords)
        {
            try
            {
                // 使用批量添加
                // Batch add
                Recordset.BatchEditor editor = profileRecords.Batch;
                editor.MaxRecordCount = 5;
                editor.Begin();
                foreach (Geometry geometry in geometries)
                {
                    profileRecords.AddNew(geometry);
                }
                editor.Update();
                profileRecords.Close();
                profileRecords.Dispose();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        /// <summary>
        /// 计算距离起点的最大距离，实际就是切割线的测地线长度
        /// Calculate the maximum distance of start point 
        /// </summary>
        /// <param name="startPoint">切割线的起点</param>
        /// Start point of line
        /// <param name="endPoint">切割线的终点</param>
        /// End point of line
        /// <returns>最大距离</returns>
        /// Maximum distance
        private Double ComputeMaxDistance(Point2D startPoint, Point2D endPoint)
        {
            try
            {
                Double maxDistance = Geometrist.ComputeGeodesicDistance(new Point2Ds(startPoint, endPoint), m_majorAxis, m_flatten);
                return maxDistance;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return -1;
            }
        }

        /// <summary>
        /// 计算最大高程和最小高程
        /// Calculate maximum and minimum height value
        /// </summary>
        /// <param name="maxElevation">最大高程</param>
        /// Max height
        /// <param name="minElevation">最小高程</param>
        /// Min height
        private void ComputeElevation(ref Double maxElevation, ref Double minElevation)
        {
            try
            {
                for (Int32 i = 0; i < m_profileLine3Ds.Count; i++)
                {
                    GeoLine3D currentline = m_profileLine3Ds[i];
                    Int32 count = currentline[0].Count;
                    for (Int32 j = 0; j < count; j++)
                    {
                        double elvation = currentline[0][j].Z;
                        if (elvation > maxElevation)
                        {
                            maxElevation = elvation;
                        }
                        if (elvation < minElevation)
                        {
                            minElevation = elvation;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        /// <summary>
        /// 计算剖面图的所有几何对象
        /// Calculate all geographic objects of profile graph
        /// </summary>
        /// <param name="maxDistance">距离起点的最大距离</param>
        /// Max distance from starting point
        /// <param name="maxElevation">最大高程</param>
        /// Max height
        /// <param name="minElevation">最小高程</param>
        /// Min height
        /// <returns>剖面图包含的几何对象的数组</returns>
        /// The array of the geographic objects of the profile graph
        private List<Geometry> ComputeAllElements(Double maxDistance, Double maxElevation, Double minElevation)
        {
            try
            {
                // 使用数组存储所有对象，包括坐标轴及刻度、文本、剖面线对象
                // Use array to store all the objects
                List<Geometry> geometries = new List<Geometry>();

                // 绘制坐标轴
                // Draw coordinates axis
                // 计算X轴
                // Calculate X axis
                GeoStyle axisStyle = new GeoStyle
                {
                    LineSymbolID = 963862,
                    LineColor = Color.Black,
                    LineWidth = 0.1
                };
                GeoLine lineX = new GeoLine();
                Point2D endX = new Point2D(m_origin.X + (m_baseWith * 1.05), m_origin.Y);
                lineX.AddPart(new Point2Ds(m_origin, endX));
                lineX.Style = axisStyle;
                geometries.Add(lineX);
                GeoText textX;
                if (SuperMap.Data.Environment.CurrentCulture == "zh-CN")
                {
                    textX = CreateText("D(距离,米)", 35, endX, SuperMap.Data.TextAlignment.TopLeft, 15, -15);
                }
                else
                {
                    textX = CreateText("D (Distance, meter)", 35, endX, SuperMap.Data.TextAlignment.TopLeft, 15, -15);
                }
                geometries.Add(textX);
                Double[] numsX = Regulate(maxDistance, 0, 10);
                for (Int32 i = 1; i <= numsX[0]; i++)
                {
                    GeoLine axisX = new GeoLine();
                    Double ratio = m_baseWith / maxDistance;
                    Point2D start = new Point2D();
                    // 将X轴最后一个刻度修改为最大距离
                    // Modify the last scale of X axis to maximum distance
                    if (numsX[i] > maxDistance)
                    {
                        start.X = m_baseWith;
                        start.Y = m_origin.Y;
                        numsX[i] = Math.Round(maxDistance, 2);
                    }
                    else
                    {
                        start.X = m_origin.X + numsX[i] * ratio;
                        start.Y = m_origin.Y;
                    }
                    Point2D end = new Point2D(start.X, start.Y - 20);
                    axisX.AddPart(new Point2Ds(start, end));
                    geometries.Add(axisX);

                    // 添加刻度文本
                    // Add scale text
                    GeoText text = CreateText(numsX[i].ToString(), 35, end, SuperMap.Data.TextAlignment.TopCenter, 0, -5);
                    geometries.Add(text);
                }

                // 计算Y轴
                // Calculate Y axis
                GeoLine lineY = new GeoLine();
                Point2D endY = new Point2D(m_origin.X, m_origin.Y + m_baseHeight + m_baseWith * 0.05);
                lineY.AddPart(new Point2Ds(m_origin, endY));
                lineY.Style = axisStyle;
                geometries.Add(lineY);
                GeoText textY;
                if (SuperMap.Data.Environment.CurrentCulture == "zh-CN")
                {
                    textY = CreateText("Z(高程,米)", 35, endY, SuperMap.Data.TextAlignment.MiddleCenter, 0, 30);
                }
                else
                {
                    textY = CreateText("Z (Height, meter)", 35, endY, SuperMap.Data.TextAlignment.MiddleCenter, 0, 30);
                }
                geometries.Add(textY);
                String elevationInfo = "Max(Z)=" + Math.Round(maxElevation, 2) + " " + "Min(Z)=" + Math.Round(minElevation, 2);
                GeoText elevationText = CreateText(elevationInfo, 30, endY, SuperMap.Data.TextAlignment.MiddleLeft, 30, -15);
                geometries.Add(elevationText);

                // 使Y轴绘制的高程范围占到Y轴总高度的三分之二
                // Make the height range draw by Y axis into 2/3 of overall Y axis height
                minElevation -= 2 * (maxElevation - minElevation);

                Double[] numsY = Regulate(maxElevation, minElevation, 10);
                minElevation = numsY[1];
                maxElevation = numsY[(Int32)numsY[0]];
                for (Int32 i = 1; i <= numsY[0]; i++)
                {
                    GeoLine axisY = new GeoLine();
                    Double move = m_baseHeight / (numsY[0] - 1);
                    Point2D start = new Point2D(m_origin.X, m_origin.Y + move * (i - 1));
                    Point2D end = new Point2D(start.X - 20, start.Y);
                    axisY.AddPart(new Point2Ds(start, end));
                    geometries.Add(axisY);

                    // 添加刻度文本
                    // Add scale text
                    GeoText text = CreateText(numsY[i].ToString(), 35, end, SuperMap.Data.TextAlignment.MiddleRight, -5, 0);
                    geometries.Add(text);
                }

                // 计算横轴和纵轴的绘制比率，即一个单位表示的长度
                // Calculate the draw ratio of X axis/Y axis
                Double ratioX = maxDistance / m_baseWith;
                Double ratioY = 0;
                ratioY = (maxElevation - minElevation) / m_baseHeight;
                // 计算要绘制的线，X轴方向为水平距离，Y轴方向为高程
                // Calculate the drawn line, X axis is horizontal, Y axis is 
                for (Int32 i = 0; i < m_profileLine3Ds.Count; i++)
                {
                    GeoLine3D profileline = m_profileLine3Ds[i];
                    Point3Ds profilePoints = profileline[0];
                    Point2Ds profilePoints2D = new Point2Ds();
                    for (Int32 j = 0; j < profilePoints.Count; j++)
                    {
                        Point2D currentPoint = new Point2D(profilePoints[j].X, profilePoints[j].Y);
                        Double distance = Geometrist.ComputeGeodesicDistance(new Point2Ds(new Point2D[] { m_startPoint, currentPoint }), m_majorAxis, m_flatten);

                        Point2D point2D = new Point2D
                        {
                            X = distance / ratioX,
                            Y = (profilePoints[j].Z - minElevation) / ratioY
                        };
                        profilePoints2D.Add(point2D);
                    }

                    if (i == m_indexOfSurface)
                    {
                        // 将地形剖面线与地形部分组成面对象来绘制
                        // Union terrain profile line and terrain to 
                        Double max = Geometrist.ComputeGeodesicDistance(new Point2Ds(new Point2D[] { m_startPoint, m_endPoint }), m_majorAxis, m_flatten);
                        profilePoints2D.Add(new Point2D(m_origin.X + max / ratioX, m_origin.Y));
                        profilePoints2D.Add(m_origin);
                        GeoRegion geoRegion = new GeoRegion();
                        geoRegion.AddPart(profilePoints2D);
                        GeoStyle style = new GeoStyle
                        {
                            FillForeColor = Color.LightBlue,
                            FillSymbolID = 2
                        };
                        geoRegion.Style = style;
                        geometries.Add(geoRegion);
                    }
                    else
                    {
                        GeoLine geoLine = new GeoLine();
                        geoLine.AddPart(profilePoints2D);
                        geometries.Add(geoLine);
                    }
                }
                return geometries;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
        }

        /// <summary>
        /// 根据指定的参数创建文本对象
        /// Create text object according to the specified parameters
        /// </summary>
        /// <param name="text">文本内容</param>
        /// Contecnts
        /// <param name="fontHeight">文本高度</param>
        /// Height
        /// <param name="reference">文本位置的参考点</param>
        /// Reference point
        /// <param name="offsetX">X方向偏移量</param>
        /// X offset
        /// <param name="offsetY">Y方向偏移量</param>
        /// Y offset
        /// <returns>创建的文本对象</returns>
        /// Text
        private GeoText CreateText(String text, Int32 fontHeight, Point2D reference, TextAlignment alignment, Int32 offsetX, Int32 offsetY)
        {
            try
            {
                GeoText geoText = new GeoText();
                TextPart part = new TextPart
                {
                    Text = text,
                    X = reference.X + offsetX,
                    Y = reference.Y + offsetY
                };
                geoText.AddPart(part);
                TextStyle textStyle = new TextStyle
                {
                    FontHeight = fontHeight,
                    IsSizeFixed = false,
                    Alignment = alignment
                };
                geoText.TextStyle = textStyle;
                return geoText;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
        }

        /// <summary>
        /// 创建用于存储二维剖面图的CAD数据集
        /// Create CAD dataset to store 2D profile graph
        /// </summary>
        /// <param name="resultName">CAD数据集的名称</param>
        /// Name of CAD dataset
        /// <returns>新创建的数据集</returns>
        /// Dataset that has been created
        private DatasetVector CreateProfileCAD(String resultName)
        {
            try
            {
                if (m_datasource.Datasets.Contains(resultName))
                {
                    m_datasource.Datasets.Delete(resultName);
                }
                DatasetVectorInfo cadInfo = new DatasetVectorInfo
                {
                    Type = DatasetType.CAD,
                    Name = resultName
                };
                DatasetVector profileCAD = m_datasource.Datasets.Create(cadInfo);
                return profileCAD;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
        }

        /// <summary>
        /// 根据最大最小值计算坐标轴步长和刻度值
        /// Calculate axis step length and scale value according to the max/min value
        /// </summary>
        /// <param name="max">最大值</param>
        /// Max
        /// <param name="min">最小值</param>
        /// Min
        /// <param name="maxAxisNum">最大刻度数</param>
        /// Max scale
        /// <returns>刻度值数组，其中第一个元素为标识刻度数目的值</returns>
        /// Scale array, the first element is the value to identify scale number
        private Double[] Regulate(Double max, Double min, Int32 maxAxisNum)
        {
            try
            {
                Double[] nums = new Double[maxAxisNum + 2];
                if (maxAxisNum < 1 || max < min)
                {
                    return null;
                }
                Double delta = max - min;
                if (delta < 1.0)
                {
                    max += (1.0 - delta) / 2.0;
                    min -= (1.0 - delta) / 2.0;
                }
                delta = max - min;

                Int32 exp = (Int32)(Math.Log(delta) / Math.Log(10)) - 2;
                Double multiplier = Math.Pow(10, exp);
                Double[] solutions = new Double[] { 1, 2, 2.5, 5, 10, 20, 25, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500 };
                Int32 i;
                for (i = 0; i < solutions.Length; i++)
                {
                    double multiCal = multiplier * solutions[i];
                    if (((Int32)(delta / multiCal) + 1) <= maxAxisNum)
                    {
                        break;
                    }
                }

                Double interval = multiplier * solutions[i];
                Double startPoint;
                if (min == 0)
                {
                    startPoint = 0;
                }
                else
                {
                    startPoint = (Math.Ceiling(min / interval) - 1) * interval;
                }
                Int32 axisIndex;
                for (axisIndex = 1; axisIndex <= maxAxisNum + 1; axisIndex++)
                {
                    nums[axisIndex] = (startPoint + interval * (axisIndex - 1));
                    if (startPoint + interval * (axisIndex - 1) > max)
                    {
                        break;
                    }
                }
                nums[0] = axisIndex;
                return nums;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
        }
    }
}
